//
// Created by 李传智 on 2018/4/12.
//

#include <GLES2/gl2.h>
#include "Shader.h"
#include "Logger.h"

#define GET_STR(x) #x // 自动将 x 转换为 "x"

// GLSL
// 1. 顶点着色器
static const char *vertexShaderCode = GET_STR(
        attribute
        vec4 vertexPosition;       // 顶点坐标
        attribute
        vec2 texturePosition;      // 纹理坐标
        varying
        vec2 outTexturePosition;   // 输出的纹理坐标
        void main() {
            outTexturePosition = vec2(texturePosition.x, 1.0 - texturePosition.y); // 坐标转换
            gl_Position = vertexPosition;
        }
);
// 2. 片元着色器，软解码和部分x86硬解码
static const char *fragmentShaderYUV420PCode = GET_STR(
        precision
        mediump float;            // 精度
        varying
        vec2 outTexturePosition;  // 顶点着色器传递的坐标
        uniform
        sampler2D yTexture;       // 输入的纹理 （不透明灰度，单像素）
        uniform
        sampler2D uTexture;
        uniform
        sampler2D vTexture;
        void main() {
            vec3 yuv;
            vec3 rgb;
            yuv.r = texture2D(yTexture, outTexturePosition).r;
            yuv.g = texture2D(uTexture, outTexturePosition).r - 0.5;
            yuv.b = texture2D(vTexture, outTexturePosition).r - 0.5;
            // yuv -> rgb 转换
            rgb = mat3(1.0, 1.0, 1.0,
                       0.0, -0.39465, 2.03211,
                       1.13983, -0.5806, 0.0) * yuv;
            // 输出像素颜色
            gl_FragColor = vec4(rgb, 1.0);
        }
);

static const char *fragmentShaderNV12Code = GET_STR(
        precision
        mediump float;            // 精度
        varying
        vec2 outTexturePosition;  // 顶点着色器传递的坐标
        uniform
        sampler2D yTexture;       // 输入的纹理 （不透明灰度，单像素）
        uniform
        sampler2D uvTexture;
        void main() {
            vec3 yuv;
            vec3 rgb;
            yuv.r = texture2D(yTexture, outTexturePosition).r;
            yuv.g = texture2D(uvTexture, outTexturePosition).r - 0.5;
            yuv.b = texture2D(uvTexture, outTexturePosition).a - 0.5;
            // yuv -> rgb 转换
            rgb = mat3(1.0, 1.0, 1.0,
                       0.0, -0.39465, 2.03211,
                       1.13983, -0.5806, 0.0) * yuv;
            // 输出像素颜色
            gl_FragColor = vec4(rgb, 1.0);
        }
);

static const char *fragmentShaderNV21Code = GET_STR(
        precision
        mediump float;            // 精度
        varying
        vec2 outTexturePosition;  // 顶点着色器传递的坐标
        uniform
        sampler2D yTexture;       // 输入的纹理 （不透明灰度，单像素）
        uniform
        sampler2D uvTexture;
        void main() {
            vec3 yuv;
            vec3 rgb;
            yuv.r = texture2D(yTexture, outTexturePosition).r;
            yuv.g = texture2D(uvTexture, outTexturePosition).a - 0.5;
            yuv.b = texture2D(uvTexture, outTexturePosition).r - 0.5;
            // yuv -> rgb 转换
            rgb = mat3(1.0, 1.0, 1.0,
                       0.0, -0.39465, 2.03211,
                       1.13983, -0.5806, 0.0) * yuv;
            // 输出像素颜色
            gl_FragColor = vec4(rgb, 1.0);
        }
);

static GLuint initShader(const char *code, GLenum type) {
    // 创建 shader
    GLuint shader = glCreateShader(type);
    if (shader == 0) {
        LOG_I("glCreateShader %d failed", type);
        return 0;
    }
    // 加载 shader
    glShaderSource(shader, 1, &code, 0);
    // 编译 shader
    glCompileShader(shader);
    // 获取编译情况
    GLint status;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        LOG_I("glCompileShader %d failed", type);
        return 0;
    }
    LOG_I("glCompileShader %d success", type);
    return shader;
}

bool Shader::init(int format) {
    close();
    mutex.lock();
    // 顶点 shader
    vertexShader = initShader(vertexShaderCode, GL_VERTEX_SHADER);
    if (vertexShader == 0) {
        mutex.unlock();
        LOG_I("initShader vertexShader failed");
        return false;
    }
    LOG_I("vertexShader init success");
    // 片元 yuv420p nv12 nv21 shader
    const char *code = fragmentShaderYUV420PCode;
    if (format == SHADER_NV12) {
        code = fragmentShaderNV12Code;
    } else if (format == SHADER_NV21) {
        code = fragmentShaderNV21Code;
    }
    fragmentShader = initShader(code, GL_FRAGMENT_SHADER);
    if (fragmentShader == 0) {
        mutex.unlock();
        LOG_E("initShader fragmentShader failed");
        return false;
    }
    LOG_I("fragmentShader init success");

    // 创建渲染程序
    program = glCreateProgram();
    if (program == 0) {
        mutex.unlock();
        LOG_E("glCreateProgram failed");
        return false;
    }
    // 渲染程序加入着色器
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);

    // 链接程序
    glLinkProgram(program);

    // 获取渲染程序的链接状态
    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    if (GL_TRUE != linkStatus) {
        mutex.unlock();
        LOG_E("glLinkProgram failed");
        return false;
    }

    glUseProgram(program);
    LOG_I("glLinkProgram success");

    // 顶点坐标
    static float vertex[] = {
            1.0f, -1.0f, 0.0f,
            -1.0f, -1.0f, 0.0f,
            1.0f, 1.0f, 0.0f,
            -1.0f, 1.0f, 0.0f
    };

    // 纹理坐标
    static float texture[] = {
            1.0f, 0.0f, // 右下
            0.0f, 0.0f,
            1.0f, 1.0f,
            0.0f, 1.0f
    };

    // 加入三维顶点数据 两个三角形组成正方形
    GLuint vertexPosition = (GLuint) glGetAttribLocation(program, "vertexPosition");
    glEnableVertexAttribArray(vertexPosition);
    // 传递顶点
    glVertexAttribPointer(vertexPosition, 3, GL_FLOAT, GL_FALSE, 12, vertex);

    // 加入纹理坐标数据
    GLuint texturePosition = (GLuint) glGetAttribLocation(program, "texturePosition");
    glEnableVertexAttribArray(texturePosition);
    // 传递纹理坐标
    glVertexAttribPointer(texturePosition, 2, GL_FLOAT, GL_FALSE, 8, texture);

    // 设置纹理层
    // 对于纹理第一层
    glUniform1i(glGetUniformLocation(program, "yTexture"), 0);
    switch (format) {
        case SHADER_YUV420P:
            // 对于纹理第二层
            glUniform1i(glGetUniformLocation(program, "uTexture"), 1);
            // 对于纹理第三层
            glUniform1i(glGetUniformLocation(program, "vTexture"), 2);
            break;
        default:
            glUniform1i(glGetUniformLocation(program, "uvTexture"), 1);
            break;
    }

    mutex.unlock();

    LOG_I("shader init success");

    return true;
}

void Shader::getTexture(unsigned int index, int width, int height, unsigned char *buf, bool alpha) {
    mutex.lock();

    unsigned int format = GL_LUMINANCE;
    if (alpha) {
        format = GL_LUMINANCE_ALPHA;
    }
    if (textures[index] == 0) {
        // 创建纹理
        glGenTextures(1, &textures[index]);
        // 设置纹理属性
        glBindTexture(GL_TEXTURE_2D, textures[index]);
        // 设置缩小和放大的过滤器
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // 设置纹理的格式和大小
        glTexImage2D(GL_TEXTURE_2D,
                     0, // 细节级别 0 默认
                     format, // gpu 内部格式，亮度，灰度图
                     width,
                     height, // 拉伸到全屏
                     0, // 边框
                     format, // 像素点的格式 亮度，灰度图 要与上面一致
                     GL_UNSIGNED_BYTE, // 像素的数据类型
                     NULL // 纹理的数据
        );
    }
    // 激活第一层纹理
    glActiveTexture(GL_TEXTURE0 + index);
    // 绑定到纹理
    glBindTexture(GL_TEXTURE_2D, textures[index]);
    // 替换纹理的内容
    glTexSubImage2D(GL_TEXTURE_2D,
                    0, 0, 0, width, height,
                    format,
                    GL_UNSIGNED_BYTE,
                    buf);
    mutex.unlock();
}

void Shader::close() {
    mutex.lock();
    if (program) {
        glDeleteProgram(program);
    }
    if (fragmentShader) {
        glDeleteShader(fragmentShader);
    }
    if (vertexShader) {
        glDeleteShader(vertexShader);
    }
    for (int i = 0; i < sizeof(textures) / sizeof(unsigned int); i++) {
        if (textures[i]) {
            glDeleteTextures(1, &textures[i]);
            textures[i] = 0;
        }
    }
    mutex.unlock();
}

void Shader::draw(int x, int y, int w, int h) {
    mutex.lock();
    if (!program) {
        //LOG_E("shader draw failed, program is NULL");
        mutex.unlock();
        return;
    }
    glViewport(x, y, w, h);
    // 三维绘制
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    mutex.unlock();
}